#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<string>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<ctype.h>
#include<sys/stat.h>
#include<fcntl.h>


using namespace std;

const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;

char* gargv[argvnum]; //全局命令行参数表
int gargc = 0;


char pwd[basesize];  //全局当前shell的工作路径
char pwdenv[basesize]; 

char* genv[argvnum]; //我的系统的环境变量

int lastcode = 0;// echo $? 查错误码   最近一次程序执行结果，成功为0 

//与重定向有关的全局变量
#define NoneRedir   0
#define InputRedir  1
#define OutputRedir 2
#define AppRedir    3

int redir = NoneRedir;
char* filename = nullptr; // 指针指向将来要重定向的文件名


// isspace 判断字符是否为空白字符
#define TrimSpace(pos) do{\
    while(isspace(*pos)){\
        pos++;\
    }\
}while(0)

string GetUserName() //获取用户名
{
    string name = getenv("USER");
    return name.empty() ? "None" : name ;
}

string GetHostName() // 获取主机名
{
    string hostname = getenv("HOSTNAME");
    return hostname.empty() ? "None" : hostname;
}

string GetPwd() //获取当前路径
{
    //pwd 直接从系统中获取 getcwd
    if( getcwd(pwd,sizeof(pwd)) == nullptr ) return "None";

    // 维护环境变量表中的pwd
    snprintf(pwdenv,sizeof(pwdenv),"PWD=%s",pwd);
    putenv(pwdenv);
    return pwd;

    // 环境变量本身就是要由shell维护的，pwd不建议直接从环境变量中获取
    // string pwd = getenv("PWD"); 
    //return pwd.empty() ? "None" : pwd;
}

string LastDir()
{
    string curr = GetPwd();
    if(curr == "/" || curr == "None") return curr;
    size_t pos = curr.rfind("/");
    if(pos == string::npos) return curr;
    return curr.substr(pos+1);
}


string MakeCommandLine() //构建命令行
{
    char command_line[basesize];
    snprintf(command_line,basesize,"[%s@%s %s]# ",\
            GetUserName().c_str(),GetHostName().c_str(),LastDir().c_str());
    return command_line;
}

void PrintCommandLine()
{
    printf("%s",MakeCommandLine().c_str());
    fflush(stdout);
}

bool GetCommandLine(char command_buffer[],int size)
{
    char* result = fgets(command_buffer,size,stdin);
    if(!result)
    {
        return false;
    }
    command_buffer[strlen(command_buffer)-1] = 0; //抵消回车键
    if(strlen(command_buffer) == 0) return false; //空串直接返回
    return true;
}


void ResetCommandLine()  //初始化命令行
{
    memset(gargv,0,sizeof(gargv));
    gargc = 0;

    // 重定向
    redir = NoneRedir;
    filename = nullptr;
}
void ParseRedir(char command_buffer[],int len) //解析重定向
{
    int end = len - 1;
    while(end >= 0)
    {
        if(command_buffer[end] == '<')
        {
            redir = InputRedir;
            filename = &command_buffer[end] + 1;
            TrimSpace(filename);
            command_buffer[end] = 0;
            break;
        }
        else if(command_buffer[end] == '>')
        {
            if(command_buffer[end-1] == '>')
            {
                redir = AppRedir;
                command_buffer[end] = 0;
                command_buffer[end-1] = 0;
                filename = &command_buffer[end] + 1;
                TrimSpace(filename);
                break;
            }
            else 
            {
                redir = OutputRedir;
                command_buffer[end] = 0;
                filename = &command_buffer[end] + 1;
                TrimSpace(filename);
                break;
            }
        }
        else
        {
            end--;
        }
    }
}
void ParseCommand(char command_buffer[])
{
    const char* sep = " ";
    gargv[gargc++] = strtok(command_buffer,sep);

    while((bool)(gargv[gargc++] = strtok(nullptr,sep)));
    gargc--;
}

void ParseCommandLine(char command_buffer[],int len) //解析命令行
{
    ResetCommandLine();
    ParseRedir(command_buffer,len);
    ParseCommand(command_buffer);
}



// 在xshell中，有些命令必须由子进程来进行
// 有些命令不能由子进程来进行，要由shell自己来执行 --- 内建命令 built command


void AddEnv(const char* item)
{
    int index = 0;
    while(genv[index])
    {
        index++;
    }
    genv[index] = (char*)malloc(strlen(item)+1);
    strncpy(genv[index],item,strlen(item)+1);
    genv[++index] = nullptr;
}

// shell自己执行命令，本质是shell调用自己的函数
bool CheckAndExecBuiltCommand()   // 检测并执行内建命令
{
    if(strcmp(gargv[0],"cd") == 0)
    {
        if(gargc == 2)
        {
            chdir(gargv[1]);
            lastcode = 0;
        }
        else 
        {
            lastcode = 1;
        }
        return true;
    }
    else if(strcmp(gargv[0],"export") == 0)
    {
        if(gargc == 2)
        {
            AddEnv(gargv[1]); //添加环境变量
            lastcode = 0;
        }
        else 
        {
            lastcode = 2;
        }
        return true;
    }
    else if(strcmp(gargv[0],"env") == 0)
    {
        for(int i = 0 ; genv[i] ; i++)
        {
            printf("%s\n",genv[i]);
        }
        lastcode = 0;
        return true;
    }
    else if(strcmp(gargv[0],"echo") == 0)
    {
        if(gargc == 2)
        {
            if(gargv[1][0] == '$')
            {
                if(gargv[1][1] == '?')
                {
                    printf("%d\n",lastcode);
                    lastcode = 0;
                }
            }
            else 
            {
                printf("%s\n",gargv[1]);
                lastcode = 0;
            }
        }
        else 
        {
            lastcode = 4;
        }
        return true;
    }
 
    return false;
}

void DoRedir()
{
     //1.重定向应该让子进程自己做
     if(redir == InputRedir)
     {
         if(filename)
         {
             int fd = open(filename,O_RDONLY);
             if(fd < 0)
             {
                 exit(2);
             }
             dup2(fd,0);
         }
         else
         {
             exit(1);
         }
     }
     else if(redir == OutputRedir)
     {
         if(filename)
         {
             int fd = open(filename,O_CREAT | O_WRONLY | O_TRUNC,0666);
             if(fd < 0)
             {
                 exit(4);
             }
             dup2(fd,1);
         }
         else
         {
             exit(3);
         }
     }
     else if(redir == AppRedir)
     {
        if(filename)
        {
            int fd = open(filename,O_CREAT | O_WRONLY | O_APPEND,0666);
            if(fd < 0)
            {
                exit(6);
            }
            dup2(fd,1);
        }
        else
        {
            exit(5);
        }
     }
     else
     {
        // do nothing
     }
}
bool ExecuteCommand()  //4.执行命令
{
    // 执行命令不能让shell自己执行，应该让子进程执行
    pid_t id = fork();
    if(id < 0) return false;
    if(id == 0)
    {
       DoRedir(); //执行重定向 

        //.  exec*
      // execvp(gargv[0],gargv); 子进程不能继承我的环境变量表
      execvpe(gargv[0],gargv,genv);

        //2. exit
        exit(1);
    }

    int status = 0;
    pid_t rid = waitpid(id,&status,0);
    if(rid > 0)
    {
        if(WIFEXITED(status))
        {
            lastcode = WEXITSTATUS(status);
        }
        else 
        {
            lastcode = 3;
        }
        return true;
    }
    return false;
}


// 作为一个shell,获取环境变量应该从系统的配置文件中来，我们做不到
// 我们直接从父shell中获取环境变量
void InitEnv()
{
    extern char** environ;
    int index = 0;
    while(environ[index])
    {
        genv[index] = (char*)malloc(strlen(environ[index])+1);
        strncpy(genv[index],environ[index],strlen(environ[index])+1);
        index++;
    }
    genv[index] = nullptr;
}
int main()
{
    InitEnv();    //获取环境变量表

    char command_buffer[basesize]; //输出型参数

    while(true)
    {
        PrintCommandLine();// 1.命令行提示符
        //sleep(100);

        if( !GetCommandLine(command_buffer,basesize) ) // 2.获取用户命令
        {
            continue;
        }

        ParseCommandLine(command_buffer,strlen(command_buffer)); // 3.分析命令
        

        if( CheckAndExecBuiltCommand() )  //检测是不是内建命令
        {
            continue;
        }
        ExecuteCommand();  //4.执行命令
    }
    return 0;
}

